package com.trinhrae.shooting.util;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author: TrinhRae
 * @Package: com.trinhrae.shooting.util
 * @Project: shooting
 * @Date: 2025/9/1 17:20
 */
@Component
public class RedisUtil {
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String INCR_EXPIRE_LUA =
            "local c = redis.call(\"incr\", KEYS[1])\n" +
                    "if c == 1 then\n" +
                    "    redis.call(\"expire\", KEYS[1], ARGV[1])\n" +
                    "elseif c == 5 then\n" +
                    "    redis.call(\"expire\", KEYS[1], ARGV[1])\n" +
                    "end\n" +
                    "return c";
    private final StringRedisTemplate stringRedisTemplate;
    private static final DefaultRedisScript<Long> INCR_EXPIRE_SCRIPT =
            new DefaultRedisScript<>(INCR_EXPIRE_LUA, Long.class);

    public RedisUtil(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 存String
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     */
    public void saveString(String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    public long incr5TimesWithExpire(String key, long expireSeconds) {
        return stringRedisTemplate.execute(
                INCR_EXPIRE_SCRIPT,
                Collections.singletonList(key),
                String.valueOf(expireSeconds)
        );
    }

    /**
     * 存Hash
     *
     * @param key     键
     * @param hk      哈希键
     * @param hv      哈希值
     * @param timeout 过期时间
     */
    public void saveHash(String key, Object hk, Object hv, long timeout) {
        redisTemplate.opsForHash().put(key, hk, hv);
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 修改hash内部值
     *
     * @param key 键
     * @param hk  哈希键
     * @param hv  哈希值
     */
    public void setHash(String key, Object hk, Object hv) {
        redisTemplate.opsForHash().put(key, hk, hv);
    }

    /**
     * 直接存Hash
     *
     * @param key     键
     * @param map     map
     * @param timeout 过期时间
     */
    public void saveHashAll(String key, HashMap map, long timeout) {
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 存Set
     *
     * @param key   键
     * @param value 值
     */
    public void saveSet(String key, Object... value) {
        redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 获取String
     *
     * @param key 关键词
     * @return value 值
     */
    public Object getValue(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取整个Hash
     *
     * @param key 整个hash的键
     * @return
     */
    public Map<Object, Object> getEntireHash(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取哈希的某个值
     *
     * @param key 键
     * @param hk  哈希键
     * @return
     */
    public Object getHash(String key, Object hk) {
        return redisTemplate.opsForHash().get(key, hk);
    }

    /**
     * 获取过期时间
     *
     * @param key 键
     * @return long 过期时间（秒）
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 扫描子key，需要配合通配符"*"
     *
     * @param pattern 父key，需要通配符
     * @return
     */
    public Set<String> scanKeys(String pattern) {
        Set<String> keys = new HashSet<>();
        redisTemplate.execute((RedisConnection connection) -> {
            Cursor<byte[]> cursor = connection.scan(
                    ScanOptions.scanOptions().match(pattern).count(1000).build()
            );
            while (cursor.hasNext()) {
                keys.add(new String(cursor.next()));
            }
            return null;
        });
        return keys;
    }


    /**
     * 删除单String
     *
     * @param key 关键词
     * @return flag 是否删除
     */
    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 批量删除String
     *
     * @param list 要删除的键列
     * @return count 被删除的数量
     */
    public Long deleteBatch(List<String> list) {
        return redisTemplate.delete(list);
    }

    /**
     * 判断键是否存在
     *
     * @param key 键
     * @return flag 是否存在
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 判断哈希键是否存在
     *
     * @param key     键
     * @param hashKey 哈希键
     * @return flag 是否存在
     */
    public boolean hasHashKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 判断Set值是否存在
     *
     * @param key   键
     * @param value set值
     * @return flag 是否存在
     */
    public boolean hasSetKey(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 获得两个Set的交集
     *
     * @param key      键
     * @param otherKey 另一个键
     * @return set 结果集
     */
    public Set<Object> setIntersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 获得两个Set的并集
     *
     * @param key      键
     * @param otherKey 另一个键
     * @return set 结果集
     */
    public Set<Object> setUnion(String key, String otherKey) {
        return redisTemplate.opsForSet().union(key, otherKey);
    }
}
